﻿namespace Kodefabrikken.DDD.Entity.Experimental
{
    // Require sub-class as generic parameter so we have the type of the object instance
    public class Identity<TKey, TSubClass> : IIdentity<TKey> where TSubClass : Identity<TKey, TSubClass>
    {
        public Identity(TKey id)
        {
            // Assert that the user has given us the correct type
            this.AssertOfType<TSubClass>();
            // We need this assertion, if not, something like this might happen
            // class SomeId : Identity<int, SomeId> ... class SomeId2 : Identity<int, SomeId>
            // This will compile, but produce incorrect result with no clue to the user

            fValue = id;
        }

        private readonly TKey fValue;

        #region IIdentity<TKey>

        public TKey Value { get { return fValue; } }

        #endregion

        #region Comparison

        #region IEquatable<IIdentity<TKey>>

        public bool Equals(IIdentity<TKey> obj)
        {
            return EqualsCommon(obj as TSubClass);
        }

        #endregion

        #region object

        public override int GetHashCode()
        {
            return Value.GetHashCode();
        }

        public override bool Equals(object obj)
        {
            return EqualsCommon(obj as TSubClass);
        }

        #endregion

        public static bool operator ==(Identity<TKey, TSubClass> a, Identity<TKey, TSubClass> b)
        {
            if ((object)a == null)
            {
                // a == b == null treated as not equal (like in SQL :), this should be Identity !!!
                return false;
            }

            return a.EqualsCommon(b);
        }

        public static bool operator !=(Identity<TKey, TSubClass> a, Identity<TKey, TSubClass> b)
        {
            return !(a == b);
        }

        private bool EqualsCommon(Identity<TKey, TSubClass> obj)
        {
            if ((object)obj == null)
            {
                return false;
            }

            // ReferenceEquals is inlined, use that instead of our own implementation
            if (ReferenceEquals(this, obj))
            {
                return true;
            }

            // Check if most derived is of same class
            // two classes with same key type but different sub-class is not equal even if the values are equal, this is Identity
            if (!this.IsSameType(obj))
            {
                return false;
            }

            return Value.Equals(obj.Value);
        }

        #endregion
    }
}
